﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace Phoenix.Xna.Framework.Input.Keyboards
{
    /// <summary>
    /// Tracks the state of a combination of keyboard keys, allowing for complex mapping of user input to engine action.
    /// </summary>
    public sealed class KeyboardKeyMap : InputMap
    {
        /// <summary>
        /// Raised when the mapped key combination is pressed.
        /// </summary>
        public event EventHandler<InputMapEventArgs<KeyboardKeyMap>> Pressed;

        /// <summary>
        /// Raised when the mapped key combination is released.
        /// </summary>
        public event EventHandler<InputMapEventArgs<KeyboardKeyMap>> Released;

        /// <summary>
        /// Raised when the mapped key combination is held.
        /// </summary>
        public event EventHandler<InputMapEventArgs<KeyboardKeyMap>> Held;

        /// <summary>
        /// A value indicating the default hold threshold.
        /// </summary>
        public static int DefaultHoldThreshold = 100;

        /// <summary>
        /// A value indicating the default repeat rate.
        /// </summary>
        public static int DefaultRepeatRate = 10;

        private int _lastRepeatTimeSpan;    // The timespan since the KeyMap was last reported as repeated.
        private int _millisecondsHeld;      // The timespan since the KeyMap was last reported as held.

        /// <summary>
        /// Gets the list of mapped keys.
        /// </summary>
        public List<Keys> MappedKeys { get; private set; }

        /// <summary>
        /// Gets or sets the key repeat rate for this KeyMap.
        /// </summary>
        public int RepeatRate { get; set; }

        /// <summary>
        /// Gets or sets the key hold threshold for this KeyMap.
        /// </summary>
        public int HoldThreshold { get; set; }

        /// <summary>
        /// Gets a value indicating that the KeyMap is up.
        /// </summary>
        public bool IsMapUp
        {
            get
            {
                bool status = false;

                foreach (Keys key in MappedKeys)
                {
                    if (key.IsKeyUp())
                    {
                        status = true;
                    }
                }

                return status;
            }
        }

        /// <summary>
        /// Gets a value indicating that the KeyMap is down.
        /// </summary>
        public bool IsMapDown
        {
            get
            {
                bool status = true;

                foreach (Keys key in MappedKeys)
                {
                    if (key.IsKeyUp())
                    {
                        status = false;
                    }
                }

                return status;
            }
        }

        /// <summary>
        /// Gets a value indicating that the KeyMap has been pressed.
        /// </summary>
        public bool WasMapPressed
        {
            get
            {
                bool status = false;

                foreach (Keys key in MappedKeys)
                {
                    if (key.WasKeyUp())
                    {
                        status = true;
                    }
                }

                return (status && IsMapDown) ? true : false;
            }
        }

        /// <summary>
        /// Gets a value indicating that the Keymap has been released.
        /// </summary>
        public bool WasMapReleased
        {
            get
            {
                bool status = true;

                foreach (Keys key in MappedKeys)
                {
                    if (key.WasKeyUp())
                    {
                        status = false;
                    }
                }

                return (status && IsMapUp) ? true : false;
            }
        }

        /// <summary>
        /// Gets a value indicating that the KeyMap was held.
        /// </summary>
        public bool WasMapHeld
        {
            get
            {
                bool status = true;

                foreach (Keys key in MappedKeys)
                {
                    if (key.WasKeyUp())
                    {
                        status = false;
                    }
                }

                return (status && IsMapDown) ? true : false;
            }
        }

        /// <summary>
        /// Initializes a new instance of the KeyMap class.
        /// </summary>
        /// <param name="keys">The collection of keys to map.</param>
        public KeyboardKeyMap(List<Keys> keys)
            : this(keys, DefaultHoldThreshold, DefaultRepeatRate) { }

        /// <summary>
        /// Initializes a new instance of the KeyMap class.
        /// </summary>
        /// <param name="keys">The collection of keys to map.</param>
        /// <param name="holdThreshold">The timespan before the KeyMap is considered held.</param>
        /// <param name="repeatRate">The timespan between repeating pressed events.</param>
        public KeyboardKeyMap(List<Keys> keys, int holdThreshold, int repeatRate)
        {
            HoldThreshold = holdThreshold;
            RepeatRate = repeatRate;
            MappedKeys = keys;
            Register();
        }

        /// <summary>
        /// Determines if the specified key is managed by this KeyMap.
        /// </summary>
        /// <param name="key">The key to search for.</param>
        /// <returns>True if this KeyMap manages the specified key, otherwise false.</returns>
        public bool Contains(Keys key)
        {
            foreach (Keys k in MappedKeys)
            {
                if (k == key)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determines if the specified keys are managed by this KeyMap.
        /// </summary>
        /// <param name="keys">The keys to search for.</param>
        /// <returns>True if this KeyMap manages the specified keys, otherwise false.</returns>
        public bool Contains(List<Keys> keys)
        {
            if (keys.Count != MappedKeys.Count)
            {
                return false;
            }

            foreach (Keys k in keys)
            {
                if (!Contains(k))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Determines if the current KeyMap is equal to the specified KeyMap.
        /// </summary>
        /// <param name="map">The KeyMap to compare to.</param>
        /// <returns>True if equal, otherwise false.</returns>
        public override bool Equals(object map)
        {
            if (Object.ReferenceEquals(this, map))
            {
                return true;
            }

            if (map != null && map is KeyboardKeyMap)
            {
                KeyboardKeyMap keyMap = map as KeyboardKeyMap;

                if (MappedKeys.Count == keyMap.MappedKeys.Count)
                {
                    foreach (Keys k in MappedKeys)
                    {
                        if (!keyMap.Contains(k))
                        {
                            return false;
                        }
                    }

                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Serves as a hash function for the KeyMap type.
        /// </summary>
        /// <returns>The hash code for this KeyMap.</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Determines if the left KeyMap is equal to the right KeyMap.
        /// </summary>
        /// <param name="left">KeyMap to the left of the operator.</param>
        /// <param name="right">KeyMap to the right of the operator.</param>
        /// <returns>True if equal, otherwise false.</returns>
        public static bool operator ==(KeyboardKeyMap left, KeyboardKeyMap right)
        {
            if (Object.ReferenceEquals(left, right))
            {
                return true;
            }

            if (!Object.ReferenceEquals(left, null))
            {
                if (Object.ReferenceEquals(right, null))
                {
                    return false;
                }
                else
                {
                    return left.Equals(right);
                }
            }

            return false;
        }

        /// <summary>
        /// Determines if the left KeyMap is not equal to the right KeyMap.
        /// </summary>
        /// <param name="left">KeyMap to the left of the operator.</param>
        /// <param name="right">KeyMap to the right of the operator.</param>
        /// <returns>True if not equal, otherwise false.</returns>
        public static bool operator !=(KeyboardKeyMap left, KeyboardKeyMap right)
        {
            return (left == right) ? false : true;
        }

        private void NotifyEvent(EventHandler<InputMapEventArgs<KeyboardKeyMap>> eventHandler)
        {
            EventHandler<InputMapEventArgs<KeyboardKeyMap>> handler = eventHandler;

            if (handler != null)
            {
                handler(this, new InputMapEventArgs<KeyboardKeyMap>(this));
            }
        }

        #region IInputMap Members

        /// <summary>
        /// Update the IInputMap.
        /// </summary>
        /// <param name="gameTime">A snapshot of the elapsed game time.</param>
        public override void Update(GameTime gameTime)
        {
            if (WasMapPressed)
            {
                _millisecondsHeld = 0;
                NotifyEvent(Pressed);
            }
            else if (WasMapReleased)
            {
                NotifyEvent(Released);
            }
            else if (WasMapHeld)
            {
                _millisecondsHeld += gameTime.ElapsedGameTime.Milliseconds;

                if (HoldThreshold > 0 && _millisecondsHeld >= HoldThreshold)
                {
                    _lastRepeatTimeSpan += gameTime.ElapsedGameTime.Milliseconds;

                    if (RepeatRate > 0 && _lastRepeatTimeSpan >= RepeatRate)
                    {
                        NotifyEvent(Held);
                        _lastRepeatTimeSpan = 0;
                    }
                }
            }
        }

        #endregion
    }
}
